/*
 * Copyright (c) 2008, 2010
 *      Toni Spets <toni.spets@iki.fi>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <assert.h>

#include "sockets.h"
#include "protocol.h"

char cncnet_game_str[4][8] = {
    "UNK",
    "C&C95",
    "RA1",
    "TS"
};

#define MAX_CLIENTS 64

// FIXME: this "times out" games in lobby too
#define IDLE_TIMEOUT 600

cncnet_client clients[MAX_CLIENTS];
uint32_t now = 0;
int sock = 0;

void reset_clients() {
    int i;
    for(i=0;i<MAX_CLIENTS;i++) {
        memset(&clients[i], 0, sizeof(cncnet_client));
    }
}

cncnet_client *new_client(struct sockaddr_in *addr, cncnet_game game, int fake)
{
    char strtmp[128];
    int i;
    for(i=0;i<MAX_CLIENTS;i++) {
        if(!clients[i].last_packet) {
            printf("> Created new client %d at %s:%d game=%d port_fake=%d\n", i, inet_ntoa(*(struct in_addr *)&addr->sin_addr.s_addr), ntohs(addr->sin_port), game, fake);
            memcpy(&clients[i].addr, addr, sizeof(struct sockaddr_in));
            clients[i].last_packet = now;
            clients[i].game = game;


            if(game == GAME_CNC95)
            {
                protocol_message(&clients[i], "EVA", "Welcome to CnCNet!");
                snprintf(strtmp, sizeof(strtmp), "You @ %s:%d\n", inet_ntoa(*(struct in_addr *)&addr->sin_addr.s_addr), ntohs(addr->sin_port));
                protocol_message(&clients[i], "EVA", strtmp);
                if(fake)
                {
                    protocol_message(&clients[i], "EVA", "Warn: Port masq in use!");
                }
                else if(ntohs(addr->sin_port) != NET_PORT)
                {
                    protocol_message(&clients[i], "EVA", "Warn: Random port detected!");
                }
            }
            else
            {
                protocol_message(&clients[i], "CnCNet", "Welcome to CnCNet!");
                snprintf(strtmp, sizeof(strtmp), "Connected from %s:%d with %s\n", inet_ntoa(*(struct in_addr *)&addr->sin_addr.s_addr), ntohs(addr->sin_port), cncnet_game_str[game]);
                protocol_message(&clients[i], "CnCNet", strtmp);

                if(fake)
                {
                    protocol_message(&clients[i], "CnCNet", "Warning: Port number masquerade in use!");
                }
                else if(ntohs(addr->sin_port) != NET_PORT)
                {
                    protocol_message(&clients[i], "CnCNet", "Warning: Random port detected, check your settings!");
                }
            }

            return &clients[i];
        }
    }
    printf("Error: Max clients (%d) reached!\n", MAX_CLIENTS);
    return NULL;
}

/* return pointer to client struct on success, NULL if MAX_CLIENTS is reached */
cncnet_client *get_client(struct sockaddr_in *addr) {
    int i;
    for(i=0;i<MAX_CLIENTS;i++) {
        if(!memcmp(&clients[i].addr, addr, sizeof(struct sockaddr_in))) {
            return &clients[i];
        }
    }
    return NULL;
}

/* broadcasts a packet to all clients in lobby */
void broadcast(cncnet_client *from, char *buf, int len)
{
    ra_buffer_t buffer;

    buffer.direct = 0x00;
    buffer.from.ip = from->addr.sin_addr.s_addr;
    buffer.from.port = from->addr.sin_port;
    assert(len <= sizeof(buffer.data));
    memcpy(buffer.data, buf, len);

    from->last_packet = now;

    int i;
    for(i=0;i<MAX_CLIENTS;i++) {
        if(clients[i].last_packet && &clients[i] != from && clients[i].game == from->game) {
            net_send(sock, &buffer, sizeof(buffer.from) + len + 1, &clients[i].addr);
        }
    }
}

int main(int argc, char **argv)
{
    int i;
    struct sockaddr_in listen;
    struct sockaddr_in addr;
    cncnet_client *cliptr;

    reset_clients();

    sock = net_socket();
    net_address_ex(&listen, INADDR_ANY, NET_PORT);
    net_bind(sock, &listen);

    ra_buffer_t buf;

    printf("> CncNet Server\n");
    int len;
    cncnet_game game;

    while((len = net_recv(sock, (char *)&buf, sizeof(ra_buffer_t), &addr))) {
        now = time(NULL);
        for(i=0;i<MAX_CLIENTS;i++) {
            if(clients[i].last_packet && clients[i].last_packet < now-IDLE_TIMEOUT) {
                printf("Removing idle client %d\n", i);
                memset(&clients[i], 0, sizeof(cncnet_client));
            }
        }

        if(buf.data[0] == 0x34 && buf.data[1] == 0x12)
        {
            game = GAME_CNC95;
        } 
        else if(buf.data[0] == 0x35 && buf.data[1] == 0x12)
        {
            game = GAME_RA1;
        }
        else if(buf.data[4] == 0x35 && buf.data[5] == 0x12)
        {
            game = GAME_TS;
        }
        else if(buf.data[4] == 0x36 && buf.data[5] == 0x12)
        {
            game = GAME_RA2;
        }
        else
        {
            printf("Ignoring unknown packet.\n");
            continue;
        }

        if(buf.direct) {
            printf("Got a direct packet from a client, this is wrong!\n");
            continue;
        }

        /* allow port faking */
        int fake = 0;
        if(buf.from.port > 1024) {
            addr.sin_port = buf.from.port;
            fake = 1;
        }

        cliptr = get_client(&addr);
        if(!cliptr) {
            cliptr = new_client(&addr, game, fake);
        }

        if(cliptr->game != game) {
            printf("> Client changed game from %d to %d, following\n", cliptr->game, game);
            cliptr->game = game;
        }

        if(cliptr) {
            broadcast(cliptr, (char *)&buf.data, len - sizeof(ra_client_t) - 1);
        }
    }

    return 0;
}
